﻿using System.Net.Sockets;
using System.Net;
using System;
using UnityEngine;
using MiscUtil.Conversion;
using LuaInterface;
namespace Network
{
    public class TcpChannel
    {
        private Socket _socket;
        private TcpStreamCoDec _streamCoDec;
        private byte[] _recvBuff;
        private ConnectionState _state;
        enum ConnectionState
        {
            STOPPED,
            CONNECTING,
            CONNECTED,
        };

        float _connectingTime = 0;
        const float ConnectTimeout = 5f;
        
        public Action<bool> OnConnectFinish;
        public Action OnDisconnect;
        public Action OnSendAck;
        public delegate void OnRevDataDelegate(LuaByteBuffer data);
        public OnRevDataDelegate OnRevData;

        public TcpChannel()
        {
            _recvBuff = new byte[1024];
            
        }

        public bool IsConnected
        {
            get { return _state == ConnectionState.CONNECTED; }
        }

        public void Tick(float deltaTime)
        {

            if (_state == ConnectionState.CONNECTING)
            {
                _connectingTime += deltaTime;

                if (_connectingTime > ConnectTimeout)
                {
                    Debug.Log("socket connect timeout");
                    Close();
                    if (OnConnectFinish != null)
                        OnConnectFinish(false);
                }
                else
                {
                    if (_socket != null)
                    {
                        bool error = _socket.Poll(0, SelectMode.SelectError);
                        bool connected = _socket.Poll(0, SelectMode.SelectWrite);

                        if (!error && connected)
                        {

                            _state = ConnectionState.CONNECTED;
                            if (OnConnectFinish != null)
                                OnConnectFinish(true);
                        }
                    }
                }
            }
            else if (_state == ConnectionState.CONNECTED)
            {

                if (!ReceiveQueue())
                    return;

                SendQueue();
            }

            byte[] msgContent;
            Receive(out msgContent);

            while (msgContent != null)
            {
                if (OnRevData != null)
                {
                    OnRevData(new LuaByteBuffer(msgContent));
                }
                Receive(out msgContent);
            }
        }

        static IPAddress GetTrueIp(System.Net.IPHostEntry iPHostEntry)
        {
            IPAddress trueIp = null;
            foreach (IPAddress address in iPHostEntry.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetworkV6)
                {
                    trueIp = address;
                }
                else if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    if (trueIp == null)
                    {
                        trueIp = address;
                    }
                }

            }
            return trueIp;
        }

        public void Connect(string host, int port)
        {
            if (_state != ConnectionState.STOPPED)
            {
                return;
            }

            try
            {
                IPAddress address = null;
                if (!IPAddress.TryParse(host, out address))
                {
                    IPHostEntry hostinfo = Dns.GetHostEntry(host);
                    address = GetTrueIp(hostinfo);
                }
                
                if (address.AddressFamily == AddressFamily.InterNetworkV6)
                {
                    _socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
                }
                else
                {
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                }
                _socket.Blocking = false;
                _socket.NoDelay = true;

                IPEndPoint endpoint = new IPEndPoint(address, port);
                Debug.Log("socket begin connect");
                _socket.Connect(endpoint);
            }
            catch (SocketException e)
            {
               // Debug.LogError("connect error " + e.Message );
            }

            _connectingTime = 0;
            _state = ConnectionState.CONNECTING;
            _streamCoDec = new TcpStreamCoDec();
        }

        public void Close()
        {
            if (_state != ConnectionState.STOPPED)
            {
                _state = ConnectionState.STOPPED;
                _streamCoDec = null;
                try
                {
                    Debug.Log("Socket Shutdown");
                    if(_socket != null)
                    {
                        _socket.Shutdown(SocketShutdown.Both);
                        _socket.Close();
                    }
                }
                catch (SocketException e)
                {
                }
                finally
                {
                    _socket = null;
                }
            }
        }

        public void Send(byte[] _data)
        {
            if (_state != ConnectionState.CONNECTED)
                return;

            if (_data == null)
                _data = new byte[0];
                        
            _streamCoDec.Encode(_data);
        }

        public void Receive(out byte[] msgContent)
        {
            msgContent = null;

            var data = _streamCoDec != null ? _streamCoDec.GetReceivedObject() : null;

            if(data != null)
            {
                msgContent = data;
            }
        }

        void SendQueue()
        {
            if (_state != ConnectionState.CONNECTED)
                return;
            try
            {
                while (true)
                {
                    byte[] sendByte = null;
                    int sendByteNum = _streamCoDec.GetSendingBytes(out sendByte);
                    if (sendByteNum == 0)
                        break;

                    int sendBytes = _socket.Send(sendByte, sendByteNum, SocketFlags.None);
                    //Debug.Log("Send Bytes: " + Binutils.HexDump(sendByte));
                    _streamCoDec.FinishSendingBytes(sendBytes);
                }
            }
            catch (SocketException e)
            {
                if (e.SocketErrorCode != SocketError.WouldBlock)
                {
                    Debug.Log("Tcp Send Error: " + e.SocketErrorCode);
                    Close();
                    if (OnDisconnect != null)
                        OnDisconnect();
                }
            }
        }

        bool ReceiveQueue()
        {
            if (_state == ConnectionState.CONNECTED)
            {
                try
                {
                    while (true)
                    {
                        int recvLen = _socket.Receive(_recvBuff);
                        if (recvLen == 0)
                        {
                            break;
                        }
                        //Debug.Log("Receive Bytes: " + Binutils.HexDump(_recvBuff));
                        _streamCoDec.Decode(_recvBuff, recvLen);
                    }
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode != SocketError.WouldBlock)
                    {
                        Debug.LogWarning("Tcp Receive Error: " + ex.SocketErrorCode + "\n" + ex.StackTrace.ToString());
                        Close();
                        if (OnDisconnect != null)
                            OnDisconnect();
                        return false;
                    }
                }
            }
            
            return true;
        }
    }
}